package cn.edu.hit.rest.controller;

import cn.edu.hit.CacheManager;
import cn.edu.hit.kg.data.GraphData;
import cn.edu.hit.kg.entity.*;
import cn.edu.hit.rest.entity.FeatureInfo;
import cn.edu.hit.rest.entity.MatchResult;
import cn.edu.hit.rest.entity.OpType;
import cn.edu.hit.rest.entity.ResultPack;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.edu.hit.kg.entity.BaseNode.nodeCache;

@RestController
public class MainController {
    private RestTemplate restTemplate;
    private GraphData graphData;

    @Value("${app.feature-base-url: http://10.13.25.185:8299/fault/}")
    private String featureBaseUrl;

    @CrossOrigin
    @RequestMapping(value="/tuiLi/fault",method= RequestMethod.GET)
    public ResultPack matchFault(@RequestParam("jiZu") String machineNum, @RequestParam("ruleId") String ruleId){
//        System.out.println("machineNum = " + machineNum);
//        System.out.println("ruleId = " + ruleId);
//        System.out.println("featureBaseUrl = " + featureBaseUrl);
//
//        FeatureInfo info = getFeatureInfo("V221012L8",machineNum);
//        System.out.println("info = " + JSONObject.fromObject(info));
        ResultPack resultPack = new ResultPack();
        resultPack.setCode("0");

        Optional<Fault> faultOptional = graphData.getIdSetByLabel("Fault").stream()
                .map(Fault::loadById)
                .filter(fault -> fault.getString("outId").equals(ruleId))
                .findAny();

        if (faultOptional.isPresent()){
            Fault fault = faultOptional.get();
            Set<Feature> features = fault.getFeatureSet();
            Map<Long,FeatureInfo> infoMap = new ConcurrentHashMap<>();
            features.forEach(feature -> {
                String outId = feature.getCode();
                FeatureInfo info = getFeatureInfo(outId,machineNum);
                infoMap.put(feature.getId(),info);
            });

            Map<Long,Boolean> conclusions = new HashMap<>();
            for (Map.Entry<Long, FeatureInfo> entry : infoMap.entrySet()) {
                conclusions.put(entry.getKey(),entry.getValue().getStatus()==1);
            }

            MatchTrack track = fault.trackByConclusions(conclusions);
            List<Reason> reasons = Reason.listReasons(fault.getId());
            List<MatchTrack> rtracks = reasons.stream()
                    .map(r->r.trackByConclusions(conclusions))
                    .collect(Collectors.toList());
            for (MatchTrack rtrack : rtracks) {
                track.getChildren().removeIf(matchTrack -> rtrack.getNode().getId() == matchTrack.getNode().getId());
            }
            track.getChildren().addAll(rtracks);

            ConcurrentHashMap<Long,Double> reasonFaultMap = new ConcurrentHashMap<>(); // P(Reason|Fault)
            ConcurrentHashMap<Long,Double> reasonFeatureMap = new ConcurrentHashMap<>(); // P(Reason|Feature)

            for (Reason reason : reasons) {
                reasonFaultMap.put(reason.getId(),reason.getRawFrequency(fault.getId(),machineNum));
                reasonFeatureMap.put(reason.getId(),reason.selfFrequency());
            }

            double p = 0;
            ConcurrentHashMap<Long,Double> reasonSubMap = new ConcurrentHashMap<>();
            for (MatchTrack rtrack : rtracks) {
                if (rtrack.getResult() == ISignalMatch.MatchResultType.TRUE){
                    for (Long reasonId : reasonFaultMap.keySet()) {
                        if (reasonId == rtrack.getNode().getId()){
                            double pp = reasonFaultMap.get(reasonId) * reasonFeatureMap.get(reasonId);
                            p += pp;
                            reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                        }else {
                            double pp = reasonFaultMap.get(reasonId) * (1 - reasonFeatureMap.get(rtrack.getNode().getId()));
                            p += pp;
                            reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                        }
                    }
                }
            }

            MatchResult matchResult = asMatchResult(track);

            for (Long reasonId : reasonSubMap.keySet()) {
                double propR = reasonSubMap.getOrDefault(reasonId,0.0) / p;
                Optional<BaseNode> solution =  BaseNode.getAllSubNodes(reasonId)
                        .map(nodeCache::get)
                        .filter(r -> r.node.hasLabel("Solution"))
                        .findFirst();

                matchResult.getChild().stream()
                        .filter(r->r.getId() == reasonId)
                        .findFirst()
                        .ifPresent(r->{
                            r.setProbability(propR);
//                            r.setZhixinDu(propR);
                            solution.ifPresent(baseNode -> r.setTreatment(baseNode.getString("solution")));
                        });
            }

            setCeDianList(matchResult,infoMap);
            resultPack.setData(matchResult.asMap());

            resultPack.setSuccessAndNotEmpty(true);
        }else {
            //数据不存在
            resultPack.setEmpty(true);
        }

        return resultPack;
    }

    public static final CacheManager cacheManager = CacheManager.getInstance();

    @CrossOrigin
    @RequestMapping(value="/tuiLi/saveFault",method= RequestMethod.GET)
    public String matchAndSave(@RequestParam("jiZu") String machineNum, @RequestParam("ruleId") String ruleId){
                Optional<Fault> faultOptional = graphData.getIdSetByLabel("Fault").stream()
                .map(Fault::loadById)
                .filter(fault -> fault.getString("outId").equals(ruleId))
                .findAny();

        if (faultOptional.isPresent()){
            Fault fault = faultOptional.get();
            Set<Feature> features = fault.getFeatureSet();
            Map<Long,FeatureInfo> infoMap = new ConcurrentHashMap<>();
            features.forEach(feature -> {
                String outId = feature.getCode();
                FeatureInfo info = getFeatureInfo(outId,machineNum);
                infoMap.put(feature.getId(),info);
            });

            Map<Long,Boolean> conclusions = new HashMap<>();
            for (Map.Entry<Long, FeatureInfo> entry : infoMap.entrySet()) {
                conclusions.put(entry.getKey(),entry.getValue().getStatus()==1);
            }

            MatchTrack track = fault.trackByConclusions(conclusions);
            List<Reason> reasons = Reason.listReasons(fault.getId());
            List<MatchTrack> rtracks = reasons.stream()
                    .map(r->r.trackByConclusions(conclusions))
                    .collect(Collectors.toList());
            for (MatchTrack rtrack : rtracks) {
                track.getChildren().removeIf(matchTrack -> rtrack.getNode().getId() == matchTrack.getNode().getId());
            }
            track.getChildren().addAll(rtracks);

            ConcurrentHashMap<Long,Double> reasonFaultMap = new ConcurrentHashMap<>(); // P(Reason|Fault)
            ConcurrentHashMap<Long,Double> reasonFeatureMap = new ConcurrentHashMap<>(); // P(Reason|Feature)

            for (Reason reason : reasons) {
                reasonFaultMap.put(reason.getId(),reason.getRawFrequency(fault.getId(),machineNum));
                reasonFeatureMap.put(reason.getId(),reason.selfFrequency());
            }

            double p = 0;
            ConcurrentHashMap<Long,Double> reasonSubMap = new ConcurrentHashMap<>();
            for (MatchTrack rtrack : rtracks) {
                if (rtrack.getResult() == ISignalMatch.MatchResultType.TRUE){
                    for (Long reasonId : reasonFaultMap.keySet()) {
                        if (reasonId == rtrack.getNode().getId()){
                            double pp = reasonFaultMap.get(reasonId) * reasonFeatureMap.get(reasonId);
                            p += pp;
                            reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                        }else {
                            double pp = reasonFaultMap.get(reasonId) * (1 - reasonFeatureMap.get(rtrack.getNode().getId()));
                            p += pp;
                            reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                        }
                    }
                }
            }

            MatchResult matchResult = asMatchResult(track);

            for (Long reasonId : reasonSubMap.keySet()) {
                double propR = reasonSubMap.getOrDefault(reasonId,0.0) / p;
                Optional<BaseNode> solution =  BaseNode.getAllSubNodes(reasonId)
                        .map(nodeCache::get)
                        .filter(r -> r.node.hasLabel("Solution"))
                        .findFirst();
                String reason_key = "fault_reason:" + machineNum + ":" + fault.getId()+":"+reasonId;
                cacheManager.getJedis().set(reason_key, ""+propR);

                matchResult.getChild().stream()
                        .filter(r->r.getId() == reasonId)
                        .findFirst()
                        .ifPresent(r->{
                            r.setProbability(propR);
//                            r.setZhixinDu(propR);
                            solution.ifPresent(baseNode -> r.setTreatment(baseNode.getString("solution")));
                        });
            }

//            setCeDianList(matchResult,infoMap);
//            resultPack.setData(matchResult.asMap());
//
//            resultPack.setSuccessAndNotEmpty(true);
            long timestamp = System.currentTimeMillis();
            String reportId = Fault.nextReportId(machineNum, timestamp);

            String jedis_key = "fault_feature:" + machineNum + ":" + fault.getId();

            if(track.getResult() == ISignalMatch.MatchResultType.TRUE){
                track.getChildren().stream().filter(tk->tk.getResult()== ISignalMatch.MatchResultType.TRUE)
//                        .peek(tk->{
//                            System.out.println("原因或特征："+tk.getNode().toString());
//                        })
                        .forEach(tk->{
                            JSONObject map = new JSONObject();
                            map.put("machineNumber", machineNum);
                            map.put("report_id", reportId);
                            map.put("fault_id", "" + fault.getId());
                            map.put("feature_id", "" + tk.getNode().getId());
                            map.put("timestamp", "" + timestamp);
                            map.put("appear_times", 1);
                            String freqKey = "feature_frequency:" + machineNum + ":" + tk.getNode().getId();
                            if (cacheManager.getJedis().exists(freqKey)) {
                                JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
                                map.put("quantum", freq.optInt("quantum"));
                                map.put("frequency", freq.getJSONObject("frequency").optInt("" + tk.getNode().getId()));
                            }
                            else {
                                if (tk.getNode().node.asMap().containsKey("quantum")) {
                                    map.put("quantum", "" + tk.getNode().getString("quantum"));
                                } else {
                                    map.put("quantum", "100");
                                }
                                String freqString = tk.getNode().getString("frequency");
                                JSONObject jsonObject = JSONObject.fromObject(freqString);
                                if (jsonObject == null || jsonObject.isNullObject()) {
                                    map.put("frequency", 10);
                                } else {
                                    map.put("frequency", "" + jsonObject.optString("" + tk.getNode().getId(), "10"));
                                }
                            }
//                            map.put("probability", matchResult.getProbability());
//                            System.out.println(map.toString());
                            cacheManager.getJedis().lpush(jedis_key, map.toString());
                        });
            }else {
                System.out.println("故障不存在!");
            }

            String newKey = "end_fault:" + reportId;
            if (cacheManager.getJedis().exists(jedis_key)) {
                long index = cacheManager.getJedis().llen(jedis_key);
                for (long i = 0; i < index; i++) {
                    String jsonString = cacheManager.getJedis().lindex(jedis_key, i);
                    cacheManager.getJedis().lpush(newKey, jsonString);
                }
                cacheManager.getJedis().del(jedis_key);
            }

            return reportId;
        }
        else {
            return "null";
        }
    }

//    @CrossOrigin
//    @RequestMapping(value="/tuiLi/endFault",method= RequestMethod.GET)
//    public String matchAndSave(@RequestParam("id") String id){
//        String jedis_key = "fault_feature:" + unitId + ":" + this.getId();
//        String newKey = "end_fault:" + lastReportId.get(unitId);
//        if (cacheManager.getJedis().exists(jedis_key)) {
//            long index = cacheManager.getJedis().llen(jedis_key);
//            for (long i = 0; i < index; i++) {
//                String jsonString = cacheManager.getJedis().lindex(jedis_key, i);
//                cacheManager.getJedis().lpush(newKey, jsonString);
//            }
//            cacheManager.getJedis().del(jedis_key);
//        }
//    }

    private  List<Integer> setCeDianList(MatchResult result,Map<Long, FeatureInfo> infoMap){
        BaseNode node = BaseNode.loadNodeById(result.getId());
        if (node instanceof Feature){
            result.setCeDianList(infoMap.getOrDefault(result.getId(),new FeatureInfo()).getCeDianList());
            return result.getCeDianList();
        }else {
            List<Integer> list = result.getChild().stream()
                    .flatMap(r->setCeDianList(r,infoMap).stream())
                    .distinct()
                    .collect(Collectors.toList());
            result.setCeDianList(list);
            return list;
        }
    }

    private static MatchResult asMatchResult(MatchTrack track){
        MatchResult result = new MatchResult();
        BaseNode node = track.getNode();
        result.setId(node.getId());
        result.setCode(node.getCode());
        result.setTreatment(null);
        result.setProbability(1);
        result.setZhixinDu(1);

        if (node instanceof Fault){
            result.setDesc1(node.getString("name"));
            result.setOp(OpType.OR);
            result.setValue(track.getResult()== ISignalMatch.MatchResultType.TRUE);
//            result.setUnknown(track.getResult()== ISignalMatch.MatchResultType.SKIP);
        }else if (node instanceof Reason){
            result.setDesc1(node.getString("name"));
            result.setOp(OpType.OR);
            result.setValue(track.getResult()== ISignalMatch.MatchResultType.TRUE);
            result.setZhixinDu(((Reason) node).selfFrequency());
        }else if (node instanceof Feature){
            result.setDesc1(node.getString("description"));
            result.setOp(OpType.OR);
            result.setValue(track.getResult()== ISignalMatch.MatchResultType.TRUE);
            result.setUnknown(track.getResult()== ISignalMatch.MatchResultType.SKIP);
//            result.setCeDianList();
        }else {//FeatureGroup
            result.setDesc1(node.getString("description"));
            if (node.getString("combineType").equalsIgnoreCase("and")){
                result.setOp(OpType.AND);
            }else if (node.getString("combineType").equalsIgnoreCase("or")){
                result.setOp(OpType.OR);
            }else if (node.getString("combineType").equalsIgnoreCase("not")){
                result.setOp(OpType.NOT);
            }

            result.setValue(track.getResult()== ISignalMatch.MatchResultType.TRUE);
            result.setUnknown(track.getResult()== ISignalMatch.MatchResultType.SKIP);
        }

        if (node instanceof Reason && track.getChildren().size()==1&&track.getChildren().get(0).getNode().node.hasLabel("FeatureGroup")){
            MatchResult subGroup = asMatchResult(track.getChildren().get(0));
            result.setOp(subGroup.getOp());
            result.setChild(subGroup.getChild());
            result.setValue(subGroup.isValue());
            result.setUnknown(subGroup.isUnknown());
        }else {
            result.setChild(track.getChildren().stream().map(MainController::asMatchResult).collect(Collectors.toList()));
        }
//        result.setCeDianList();

        return result;
    }

    private FeatureInfo getFeatureInfo(String code,String machineNum){
        String result = restTemplate.getForObject(featureBaseUrl+code+"?unit="+machineNum,String.class);
//        String result = restTemplate.getForObject("http://127.0.0.1:8080/feature.json",String.class);
//        System.out.println("result = " + result);
        JSONObject jsonObject = JSONObject.fromObject(result);
        FeatureInfo info = new FeatureInfo();
        info.setCode(code);

        JSONObject data = jsonObject.getJSONObject("data");
//        System.out.println("data = " + data);
        info.setStatus(data.getInt("status"));
        JSONArray clist = data.optJSONArray("ceDianList");
        for (int i = 0; i < clist.size(); i++) {
            info.getCeDianList().add(clist.getInt(i));
        }
        return info;
    }

    @Autowired
    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Autowired
    public void setGraphData(GraphData graphData) {
        this.graphData = graphData;
    }

    public void setFeatureBaseUrl(String featureBaseUrl) {
        this.featureBaseUrl = featureBaseUrl;
    }
}
